﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace Snowflake.Entity
{
    /// <summary>
    /// Represent a collection of EntityObjects that can be accessed by index and saved in the database
    /// </summary>
    /// <typeparam name="TEntity">Type of elements in the EntityList</typeparam>
    [Serializable]
    public class EntityList<TEntity> : IEnumerable<TEntity> where TEntity : EntityObject<TEntity>, new()
    {
        private IList<TEntity> _List;

        #region Constructors
        /// <summary>
        /// Initialize the new instance of the Snowflake.Entity.EntityList{TEntity} class that is empty.
        /// </summary>
        public EntityList()
            : this(new List<TEntity>())
        {
        }

        /// <summary>
        /// Initialize the new instance of the Snowflake.Entity.EntityList{TEntity} from specified IList source.
        /// </summary>
        /// <param name="list"></param>
        internal EntityList(IList<TEntity> list)
        {
            this._List = list;
            this.AcceptChanges();
        }
        #endregion

        #region GetEnumerator
        /// <summary>
        /// Returns interrator that iterates through the collection.
        /// </summary>
        /// <returns>IEnumerator of entity object</returns>
        public IEnumerator<TEntity> GetEnumerator()
        {
            foreach (TEntity entity in this._List)
                if (entity.State != EntityObjectState.Deleted && entity.State != EntityObjectState.Detached)
                    yield return entity;
        }

        IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator() as IEnumerator;
        }
        #endregion

        #region Insert
        /// <summary>
        /// Add entity object in the collection and sets the object state to Added
        /// </summary>
        /// <param name="entity">The EntityObject that will be added in the collection.</param>
        public void Insert(TEntity entity)
        {
            if (entity == null)
                throw new ArgumentNullException("Argument 'entity' cannot be null.");

            if (entity.EntityList == null)
                entity.EntityList = this;
            else if (entity.EntityList != this)
                throw new InvalidOperationException("This entity is owned by another EntityList");

            if (entity.State != EntityObjectState.Detached)
                throw new InvalidOperationException("This entity has different state. It should be 'EntityState.Detached'.");

            entity.State = EntityObjectState.Added;

            if (!this._List.Contains(entity))
                this._List.Add(entity);
        }
        #endregion

        #region Delete
        /// <summary>
        /// Delete and remove entity object from the collection and sets the object state to Deleted or Detached
        /// </summary>
        /// <param name="entity">The EntityObject that will be deleted and removed collection.</param>
        public void Delete(TEntity entity)
        {
            if (entity == null)
                throw new ArgumentNullException("Argument 'entity' cannot be null.");

            if (entity.EntityList != this)
                throw new InvalidOperationException("This entity is owned by another EntityList");

            if (entity.State == EntityObjectState.Added)
                entity.State = EntityObjectState.Detached;
            else
                entity.State = EntityObjectState.Deleted;

        }
        #endregion

        #region AcceptChanges
        /// <summary>
        /// Accept all changes of the objects in the collection
        /// </summary>
        public void AcceptChanges()
        {
            foreach (TEntity entity in this._List)
                this.AcceptChanges(entity);
        }

        internal void AcceptChanges(TEntity entity)
        {
            if (entity.State == EntityObjectState.Deleted)
            {
                entity.State = EntityObjectState.Detached;
                entity.ResetValues();
            }
            else
                entity.State = EntityObjectState.Unchanged;

            if (entity.EntityList == null)
                entity.EntityList = this;

            entity.RestorableValueManager.AcceptChanges();
        }
        #endregion

        #region Reject Changes
        /// <summary>
        /// Reject all changes of the objects in the collection
        /// </summary>
        public void RejectChanges()
        {
            foreach (TEntity entity in this._List)
                this.RejectChanges(entity);
        }

        internal void RejectChanges(TEntity entity)
        {
            if (entity.State == EntityObjectState.Added)
            {
                entity.State = EntityObjectState.Detached;
                entity.ResetValues();
                entity.RestorableValueManager.AcceptChanges();
            }
            else
                entity.RestorableValueManager.RejectChanges();

        }
        #endregion

        #region NewEntity
        /// <summary>
        /// Factory method that create new entity by specified type
        /// </summary>
        /// <returns>New entity in detached state</returns>
        public TEntity NewEntity()
        {
            TEntity entity = new TEntity();
            entity.EntityList = this;
            return entity;
        }
        #endregion

        #region GetChanges
        /// <summary>
        /// Gets array of entity objects by their entity state
        /// </summary>
        /// <param name="state">The state of the objects that you want to get</param>
        /// <returns>Array of EntityObject elements</returns>
        public TEntity[] GetChanges(EntityObjectState state)
        {
            var query = from item in this._List
                        where item.State == state
                        select item;

            TEntity[] result = query.ToArray();
            return result;
        }
        #endregion

        #region Count
        /// <summary>
        /// Gets the number of elements contained in EntityList
        /// </summary>
        public int Count
        {
            get
            {
                return this._List.Count<TEntity>(entity => entity.State != EntityObjectState.Deleted && entity.State != EntityObjectState.Detached);
            }
        }
        #endregion

        #region Indexed Property
        /// <summary>
        /// Gets or sets the element at specified index
        /// </summary>
        /// <param name="index">Zero-based index of element to get or set</param>
        /// <returns>The element specified by the index</returns>
        public TEntity this[int index]
        {
            get
            {
                int currentIndex = 0;
                int indexIterator = 0;

                foreach (TEntity entity in this._List)
                {
                    EntityObjectState state = entity.State;

                    if (state != EntityObjectState.Detached)
                    {
                        if (indexIterator == index)
                            return this._List[currentIndex];
                        else
                            indexIterator++;

                    }

                    currentIndex++;
                }

                throw new IndexOutOfRangeException("Index " + index + " is out of range.");
            }
            set
            {
                int currentIndex = 0;
                int indexIterator = 0;

                foreach (TEntity entity in this._List)
                {
                    EntityObjectState state = entity.State;

                    if (state != EntityObjectState.Detached)
                    {
                        if (indexIterator == index)
                        {
                            this._List[currentIndex] = value;
                            return;
                        }
                        else
                            indexIterator++;

                    }
                    currentIndex++;
                }

                throw new IndexOutOfRangeException("Index " + index + " is out of range.");
            }
        }
        #endregion

        #region OrderBy
        /// <summary>
        /// Sorts all elements of EntityList
        /// </summary>
        /// <typeparam name="TKey">The type of value that will sort by</typeparam>
        /// <param name="keySelector">Sorting selector</param>
        public void OrderBy<TKey>(Func<TEntity, TKey> keySelector)
        {
            OrderBy<TKey>(keySelector, false);
        }

        /// <summary>
        /// Sorts all elements of EntityList in descending order
        /// </summary>
        /// <typeparam name="TKey">The type of value that will sort by</typeparam>
        /// <param name="keySelector">Sorting selector</param>
        public void OrderByDescending<TKey>(Func<TEntity, TKey> keySelector)
        {
            OrderBy<TKey>(keySelector, true);
        }

        /// <summary>
        /// Sorts all elements of EntityList
        /// </summary>
        /// <typeparam name="TKey">The type of value that will sort by</typeparam>
        /// <param name="keySelector">Sorting selector</param>
        /// <param name="descending">Specified the order of sorting</param>
        private void OrderBy<TKey>(Func<TEntity, TKey> keySelector, bool descending)
        {
            if (this._List != null && this._List.Count > 0)
            {
                IOrderedEnumerable<TEntity> sortedEntities = null;

                if (descending)
                    sortedEntities = this._List.OrderByDescending(keySelector);
                else
                    sortedEntities = this._List.OrderBy(keySelector);

                this._List = sortedEntities.ToList();
            }
        }
        #endregion

        #region Clear
        public void Clear()
        {
            foreach (TEntity entity in this._List)
            {
                if (entity.State != EntityObjectState.Detached)
                {
                    entity.State = EntityObjectState.Detached;
                    entity.ResetValues();
                }
            }
        }
        #endregion

        #region IndexOf
        /// <summary>
        /// 
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        public int IndexOf(TEntity entity)
        {
            // TO DO
            // See property indexer

            if (entity.EntityList != this)
                throw new InvalidOperationException("This entity is owned by another EntityList");

            if (entity.State == EntityObjectState.Detached)
                return -1;

            int index = 0;

            foreach (TEntity entityObj in this._List)
            {
                if (entityObj.State != EntityObjectState.Detached)
                {
                    if (entityObj == entity)
                        return index;

                    index++;
                }
            }

            return -1;
        }
        #endregion

        #region Contains
        /// <summary>
        /// 
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        public bool Contains(TEntity entity)
        {
            int index = this.IndexOf(entity);
            return index >= 0;
        }
        #endregion

    }
}
